/*
 * @(#)UrlNormalizer.java	1.0 05/01/07
 *
 * Copyright 2004 Fabio Gasparetti. All rights reserved.
 */

package org.tabularium.net;

import java.net.URL;
import java.lang.String;
import java.util.regex.*;
import java.net.URLEncoder;
import java.io.UnsupportedEncodingException;
import java.text.StringCharacterIterator;

/**
 * Utilities to manipulate URLs.
 * 
 * @author Fabio Gasparetti
 * @version 1.0, 05/01/07
 */
public class UrlNormalizer {

	private UrlNormalizer() {
	}

	/**
	 * Converts URLs in the canonical form, i.e.:
	 * <ol>
	 * <li> host and protocol lower case
	 * <li> default port omitted
	 * <li> no fragments
	 * <li> /./ and /../ resolution
	 * <li> add "/" if no file part is specified.
	 * </ol>
	 * 
	 * @param url
	 *            <code>URL</code> to convert.
	 * @return new <code>URL</code> object in canonical form.
	 * @since 1.0
	 */
	public static URL normalize(URL url) {
		// check null reference
		if (url == null) {
			return null;
		}
		// protocol to lower case
		String protocol = url.getProtocol().toLowerCase();
		// host to lower case
		String host = url.getHost().toLowerCase();
		String path = url.getPath();
		String query = url.getQuery();

		int port = url.getPort();
		if (port == UrlNormalizer.defaultPort(url)) {
			port = -1;
		}
		// null file
		if (path.length() == 0) {
			path = new String("/");
		}
		// converts double // and /./ to /
		path = path.replaceAll("//", "/");
		path = path.replaceAll("/[.]/", "/");
		// removes /../
		String regex = new String("\\/[^\\/]+\\/[.]{2,2}\\/");
		while (true) {
			int len = path.length();
			path = path.replaceFirst(regex, "/");
			if (len - path.length() == 0)
				break;
		}
		if (query != null) {
			path = path.concat("?" + query);
		}
		// file string already contains the query part
		URL u = url;
		try {
			u = new URL(protocol, host, port, path);
		} catch (Exception ex) {
		}
		return u;
	}

	// if it is impossible to normalize the method returns the given url
	public static String normalize(String url) {
		String ret = url;
		try {
			URL u = new URL(url);
			u = UrlNormalizer.normalize(u);
			if (u != null)
				ret = u.toString();
		} catch (Exception ex) {
		}
		return ret;
	}

	/**
	 * Removes the index pages (such as: Index.asp, index.html, etc.) and #ref.
	 * 
	 * @param url
	 *            <code>URL</code> to convert.
	 * @return new <code>URL</code> object in canonical form.
	 * @since 1.0
	 */
	public static URL removeIndexPage(URL url) {
		if (url == null)
			return null;

		String protocol = url.getProtocol();
		String host = url.getHost();
		int port = url.getPort();
		String file = url.getFile();
		/*
		 * if (port == UrlNormalizer.defaultPort(url)) { port = -1; }
		 */

		// null file
		StringBuffer sb = new StringBuffer();
		// removes index.htm, index.html, index.php, index.php3, index.asp,
		// default.asp, default.htm, default.html
		Pattern p = Pattern
				.compile("^([^?]+\\/)([Ii][Nn][Dd][Ee][Xx][.][Hh][Tt][Mm][Ll]??|[Dd][Ee][Ff][Aa][Uu][Ll][Tt][.][Hh][Tt][Mm][Ll]??|[Dd][Ee][Ff][Aa][Uu][Ll][Tt][.][Aa][Ss][Pp]|[Ii][Nn][Dd][Ee][Xx][.][Aa][Ss][Pp]|[Ii][Nn][Dd][Ee][Xx][.][Pp][Hh][Pp][3]??)([?].*$|$)");
		Matcher m = p.matcher(file);
		sb = new StringBuffer();
		if (m.find()) {
			m.appendReplacement(sb, "$1$3");
		}
		m.appendTail(sb);
		file = sb.toString();

		URL u = null;
		try {
			u = new URL(protocol, host, port, file);
		} catch (Exception ex) {
		}
		return u;
	}

	// if it is impossible to remove the index page the method returns the
	// given url
	public static String removeIndexPage(String url) {
		String ret = url;
		try {
			URL u = new URL(url);
			u = UrlNormalizer.removeIndexPage(u);
			if (u != null)
				ret = u.toString();
		} catch (Exception ex) {
		}
		return ret;
	}

	/**
	 * Translates url's escaped sequences in ASCII characters, e.g. "%20" -> " "
	 */
	public static URL unescape(URL url) {
		if (url == null)
			return null;
		String file = url.getFile();
		if (file.length() == 0)
			return url;
		String protocol = url.getProtocol();
		String host = url.getHost();
		int port = url.getPort();

		Pattern p = Pattern.compile("[%]([0-9a-fA-F]{2})");
		Matcher m = p.matcher(file);
		StringBuffer sb = new StringBuffer();
		boolean go = m.find();
		while (go) {
			String s = m.group(1);
			char ch = (char) Integer.parseInt(s, 16);
			if (ch != '$') {
				m.appendReplacement(sb, "" + ch);
			} else {
				m.appendReplacement(sb, "\\" + ch);
			}
			go = m.find();
		}
		// append #ref
		String ref = url.getRef();
		m.appendTail(sb);
		if (ref != null) {
			sb.append("#" + ref);
		}
		file = sb.toString();
		URL u = null;
		try {
			u = new URL(protocol, host, port, file);
		} catch (Exception ex) {
		}
		return u;
	}

	public static String unescape(String url) {
		String ret = null;
		try {
			URL u = new URL(url);
			u = UrlNormalizer.unescape(u);
			if (u != null)
				ret = u.toString();
		} catch (Exception ex) {
		}
		return ret;
	}

	public static String unescapeString(String s) {
		Pattern p = Pattern.compile("[%]([0-9a-fA-F]{2})");
		Matcher m = p.matcher(s);
		StringBuffer sb = new StringBuffer();
		boolean go = m.find();
		while (go) {
			String s1 = m.group(1);
			char ch = (char) Integer.parseInt(s1, 16);
			if ((ch != '$') && (ch != '\\')) {
				m.appendReplacement(sb, "" + ch);
			} else {
				m.appendReplacement(sb, "\\" + ch);
			}
			go = m.find();
		}
		m.appendTail(sb);
		return sb.toString();
	}

	/**
	 * Translates all the space occurences in "%20" strings in the File url
	 * component (the Ref component is not considered).
	 */
	public static URL removeSpaces(URL url) {
		// check null reference
		if (url == null) {
			return null;
		}
		// protocol to lower case
		String protocol = url.getProtocol();
		// host to lower case
		String host = url.getHost();
		int port = url.getPort();
		// null file
		String file = url.getFile();
		if (file.length() == 0) {
			return url;
		}
		// removes /../
		Pattern p = Pattern.compile("[ ]");
		Matcher m = p.matcher(file);
		StringBuffer sb = new StringBuffer();
		boolean go = m.find();
		while (go) {
			String s = m.group(0);
			m.appendReplacement(sb, "%20");
			go = m.find();
		}
		// append #ref
		String ref = url.getRef();
		m.appendTail(sb);
		if (ref != null) {
			sb.append("#" + ref);
		}
		file = sb.toString();
		URL u = null;
		try {
			u = new URL(protocol, host, port, file);
		} catch (Exception ex) {
		}
		return u;
	}

	public static String removeSpaces(String url) {
		String ret = null;
		try {
			URL u = new URL(url);
			u = UrlNormalizer.removeSpaces(u);
			if (u != null)
				ret = u.toString();
		} catch (Exception ex) {
		}
		return ret;
	}

	/**
	 * Tries to build a unrelativized url using a base url. A <code>null</code>
	 * is returned if the url is malformed.
	 */
	public static String unrelativizeUrl(String baseUrl, String url) {
		URL unrelUrl = null, burl;
		try {
			if (baseUrl != null) {
				burl = new URL(baseUrl);
				unrelUrl = new URL(burl, url);
			} else {
				unrelUrl = new URL(url);
			}
		} catch (Exception ex) {
			return null;
		}
		return unrelUrl.toString();
	}

	/**
	 * Return default port for protocol specified in url parameter. This method
	 * is defined in URL jdk class but itsn't implemented yet, or not?
	 * 
	 * @param url
	 *            url that contain protocol
	 * @return default port
	 */
	public static int defaultPort(URL url) {
		if (url == null) {
			return -1;
		}
		String protocol = url.getProtocol().toLowerCase();
		if (protocol.equals("http"))
			return 80;
		if (protocol.equals("ftp"))
			return 21;
		if (protocol.equals("gopher"))
			return 70;
		return -1;
	}

	/** THE FOLLOWING TWO METHODS HAVE BEEN TAKEN FROM ANOTHER CLASS */

	/**
	 * Synonym for <tt>URLEncoder.encode(String, "UTF-8")</tt>.
	 * 
	 * <P>
	 * Used to ensure that HTTP query strings are in proper form, by escaping
	 * special characters such as spaces.
	 * 
	 * <P>
	 * An example use case for this method is a login scheme in which, after
	 * successful login, the user is redirected to the "original" target
	 * destination. Such a target might be passed around as a request parameter.
	 * Such a request parameter will have a URL as its value, as in
	 * "LoginTarget=Blah.jsp?this=that&blah=boo", and would need to be
	 * URL-encoded in order to escape its special characters.
	 * 
	 * <P>
	 * It is important to note that if a query string appears in an
	 * <tt>HREF</tt> attribute, then there are two issues - ensuring the query
	 * string is valid HTTP (it is URL-encoded), and ensuring it is valid HTML
	 * (ensuring the ampersand is escaped).
	 */
	public static String encodeForURL(String aURLFragment) {
		String result = null;
		try {
			result = URLEncoder.encode(aURLFragment, "UTF-8");
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException("UTF-8 not supported", ex);
		}
		return result;
	}

	/**
	 * Replace characters having special meaning <em>inside</em> HTML tags
	 * with their escaped equivalents, using character entities such as
	 * <tt>'&amp;'</tt>.
	 * 
	 * <P>
	 * The escaped characters are :
	 * <ul>
	 * <li> <
	 * <li> >
	 * <li> "
	 * <li> '
	 * <li> \
	 * <li> &
	 * </ul>
	 * 
	 * <P>
	 * This method ensures that arbitrary text appearing inside a tag does not
	 * "confuse" the tag. For example, <tt>HREF='Blah.do?Page=1&Sort=ASC'</tt>
	 * does not comply with strict HTML because of the ampersand, and should be
	 * changed to <tt>HREF='Blah.do?Page=1&amp;Sort=ASC'</tt>. This is
	 * commonly seen in building query strings. (In JSTL, the c:url tag performs
	 * this task automatically.)
	 */
	public static String encodeForHTMLTag(String aTagFragment) {
		final StringBuffer result = new StringBuffer();

		final StringCharacterIterator iterator = new StringCharacterIterator(
				aTagFragment);
		char character = iterator.current();
		while (character != StringCharacterIterator.DONE) {
			if (character == '<') {
				result.append("&lt;");
			} else if (character == '>') {
				result.append("&gt;");
			} else if (character == '\"') {
				result.append("&quot;");
			} else if (character == '\'') {
				result.append("&#039;");
			} else if (character == '\\') {
				result.append("&#092;");
			} else if (character == '&') {
				result.append("&amp;");
			} else {
				// the char is not a special one
				// add it to the result as is
				result.append(character);
			}
			character = iterator.next();
		}
		return result.toString();
	}

	// debug
	public static void main(String[] args) {
		System.out
				.println(removeIndexPage("http://www.state.gov/www/global/general_foreign_policy/xindex.html"));
	}
}
